package vip.wangwenhao.sso.config.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import vip.wangwenhao.autoconfigure.constants.Oauth2Constants;
import vip.wangwenhao.common.enums.RoleEnum;
import vip.wangwenhao.sso.config.security.filter.CustomUsernamePasswordAuthenticationFilter;
import vip.wangwenhao.sso.config.security.filter.TokenLoginAuthenticationFilter;
import vip.wangwenhao.sso.config.security.handler.CustomAuthenticationFailureHandler;
import vip.wangwenhao.sso.config.security.handler.CustomAuthenticationSuccessHandler;
import vip.wangwenhao.sso.config.security.handler.CustomLogoutSuccessHandler;

import javax.sql.DataSource;
import java.util.Arrays;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    /**
     * 自定义UserDetailsService注入
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private TokenLoginAuthenticationFilter tokenLoginAuthenticationFilter;

    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Value("${authentication.oauth.rememberMe.tokenValidityInSeconds}")
    private Integer tokenValidityInSeconds;

    @Value("${spring.application.name}")
    private String applicationName;

    /**
     * 配置匹配用户时密码规则
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new CustomBCryptPasswordEncoder();
    }

    @Bean
    public CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter() {
        return new CustomUsernamePasswordAuthenticationFilter(authenticationManagerBean(), customAuthenticationFailureHandler, customAuthenticationSuccessHandler);
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        // 如果token表不存在，使用下面语句可以初始化该表；若存在，请注释掉这条语句，否则会报错。
        //tokenRepository.setCreateTableOnStartup(true);
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

    @Bean
    public CustomTokenAuthenticationProvider customTokenAuthenticationProvider() {
        CustomTokenAuthenticationProvider customTokenAuthenticationProvider = new CustomTokenAuthenticationProvider();
        return customTokenAuthenticationProvider;
    }

    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() {
        CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();
        customAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        customAuthenticationProvider.setUserDetailsService(userDetailsService);
        return customAuthenticationProvider;
    }

    @Bean
    public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
        PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthenticatedAuthenticationProvider();
        preAuthenticatedAuthenticationProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper(userDetailsService));
        return preAuthenticatedAuthenticationProvider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login")
                .and()
                .logout()
                .logoutSuccessHandler(customLogoutSuccessHandler)
                .logoutSuccessUrl("/login")
                .and()
                .authorizeRequests()
                .antMatchers("/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "v2/api-doc/**").hasAuthority(RoleEnum.admin.name())
                .antMatchers("/api/user", "/error/**", "/token/**", "/login", "/static/**", "/hystrix.stream", "/favicon.ico").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .addFilterBefore(tokenLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterAt(customUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                // 声明有效时间,单位是s
                .tokenValiditySeconds(tokenValidityInSeconds)
                // 进行校验的service
                .userDetailsService(userDetailsService)
                .and()
                .csrf()
                .disable()
                .headers().frameOptions().disable();
    }

    /**
     * 配置全局设置
     *
     * @param auth
     * @throws Exception
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
//                .authenticationProvider(customAuthenticationProvider())
                //设置UserDetailsService以及密码规则
                .userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    }

    /**
     * 排除/hello路径拦截
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(Oauth2Constants.OAUTH_ALLOW_LINK_LIST);
    }

    /**
     * 不定义没有password grant_type,密码模式需要AuthenticationManager支持
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() {
        ProviderManager authenticationManager = new ProviderManager(Arrays.asList(customAuthenticationProvider(), preAuthenticatedAuthenticationProvider(), customTokenAuthenticationProvider()));
        //不擦除认证密码，擦除会导致TokenBasedRememberMeServices因为找不到Credentials再调用UserDetailsService而抛出UsernameNotFoundException
        authenticationManager.setEraseCredentialsAfterAuthentication(false);
        return authenticationManager;
    }

    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return this.userDetailsService;
    }

    /**
     * 开启全局方法拦截
     */
    @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
    public static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
        @Override
        protected MethodSecurityExpressionHandler createExpressionHandler() {
            return new OAuth2MethodSecurityExpressionHandler();
        }

    }

    @Override
    protected UserDetailsService userDetailsService() {
        return userDetailsService;
    }

}